/*
    Coder : Dzlua
    Email : 505544956@qq.com
    Time  : 2017/07/27
*/
#include "player.h"
#include "../decoder/decoder.h"
#include "edui/edui.h"
#include "dzlua/dzlua.h"
#include <time.h>

using namespace edui;

namespace {
    std::mutex g_mutex_texture_;
    ITexture * g_texture_video_ = nullptr;
}

namespace dzlua {
namespace av {

class PlayerImpl
        : public EventHandler
        , public Player {
public:
    PlayerImpl();
    ~PlayerImpl();
public:
    virtual bool Init() override;
    virtual void Quit() override;
    virtual void Run() override;
    virtual bool Open(const std::string &file) override;
    virtual void Close() override;
    virtual void Play() override;
    virtual void Stop() override;
    virtual void Pause() override;
    virtual void Continue() override;
    virtual void Seek(double pos) override;
protected:
    bool openVideo();
    bool openAudio();
    Uint32 fillVideoData();
protected:
    void setClock(double time);
    double getClock();
    // return millisecond
    int32_t delayTime(double time);
protected:
    void onButtonCloseClick(Button* btn);
    void onHandleMsg(Msg*, bool*);
    void onWndCtrlRender(IRender*, const Rect*, bool*);
protected:
    std::shared_ptr<Decoder> decoder_;
    IWindow *window_, *ctrlwnd_;
    //ITexture *texture_video_;
    std::shared_ptr<TimerID> timer_video_;
    std::mutex mutex_texture_;

    std::string file_;

    std::shared_ptr<VideoData> data_video_;
    std::shared_ptr<AudioData> data_audio_;
    
    std::atomic<double> clock_;
    bool video_only_;
};

//-----------------------//
std::shared_ptr<Player> Player::NewInterface() {
    return std::make_shared<PlayerImpl>();
}

//-----------------------//
PlayerImpl::PlayerImpl()
        : decoder_(nullptr)
        , window_(nullptr)
        , ctrlwnd_(nullptr)
        , timer_video_(nullptr)
        //, texture_video_(nullptr)
        , data_video_(nullptr)
        , data_audio_(nullptr)
        , clock_(0)
        , video_only_(false) {
    decoder_ = Decoder::NewInterface();
}

PlayerImpl::~PlayerImpl() {
    this->Quit();
}

//-----------------------//
bool PlayerImpl::Init() {
    if (!IEnginePtr->Init()){
        Log("Unable to initialize edui: %s", IEnginePtr->GetError());
        return false;
    }

    auto cur_dir = FilePath::MoudlePath();
    IEnginePtr->SetXmlDir(cur_dir->string().c_str());
    IEnginePtr->SetStringGroup("Chinese");

    const char* res_str = "<Res>"
            "<Font name=\"sh-12\" source=\"fonts/simhei.ttf\" size=\"12\"/>"
            "<Font name=\"sh-18\" source=\"fonts/simhei.ttf\" size=\"18\"/>"
            "<Image name=\"logo\" source=\"images/32X32.ico\"/>"
            "<Image name=\"close\" source=\"images/close.png\"/>"
            "<String name=\"warning\" group=\"English\" value=\"edplayer\"/>"
            "<String name=\"warning\" group=\"Chinese\" value=\"ED播放器UI\"/>"
        "</Res>";
    if ( !IEnginePtr->LoadResFromStr(res_str) ) {
        Log("edui error LoadResFromStr: %s", IEnginePtr->GetError());
        return false;
    }

    window_ = IEnginePtr->GetWindowInterface();
    if (!window_) {
        Log("edui error alloc window interface: %s", IEnginePtr->GetError());
        return false;
    }

    bool bOK = window_->Create("edplayer", "edplayer", WINDOWPOS_CENTERED, WINDOWPOS_CENTERED, 640, 480, WINDOW_BORDERLESS);
    if (!bOK){
        window_->Release();
        window_ = nullptr;
        Log("edui error Create window: %s", IEnginePtr->GetError());
        return false;
    }

    const char* wndxml =
        "<Window title=\"edplayer\" size=\"640,480\" caption=\"0,0,0,30\">"
            "<VerticalLayout name=\"root\">"
                "<HorizontalLayout height=\"30\" color=\"dst:bk;color:#FFFFFFFF;\">"
                    "<Control padding=\"5\" size=\"30,30\" image=\"dst:bk;image:(img:logo;);\"/>"
                    "<Label padding=\"3\" autowidth=\"true\" font=\"sh-18\" translatetext=\"true\" text=\"warning\"/>"
                    "<Label name=\"title_file_name\" align=\"left\" padding=\"3\"/>"
                    "<Button name=\"close\" size=\"30,25\" statusimage=\"dst:(normal,hot,pressed);status:(img:close;size:30,25;num:3;type:v1;);\"/>"
                "</HorizontalLayout>"
                "<VerticalLayout>"
                    "<WndCtrl name=\"wndctrl\"/>"
                    "<HorizontalLayout height=\"30\">"
                        "<Control/>"
                        "<Label name=\"label_now_time\" autowidth=\"true\" font=\"sh-18\" color=\"dst:text;color:#FFFFFFFF;\"/>"
                    "</HorizontalLayout>"
                "</VerticalLayout>"
            "</VerticalLayout>"
        "</Window>";
    if ( !window_->LoadLayoutFromStr(wndxml) ) {
        Log("edui window error LoadLayoutFromStr: %s", IEnginePtr->GetError());
        return false;
    }

    {
        // timer
        timer_video_ = std::shared_ptr<TimerID>(
            new TimerID
            , [](TimerID* id) {
                IEnginePtr->RemoveTimer(*id);
                delete id;
            });
        if (!timer_video_) {
            Log("edui Unable to alloc TimerID : %s", IEnginePtr->GetError());
            return false;
        }

        *timer_video_ = window_->AddTimer(
            40,
            [](TimerID id, Uint32 interval, IWindow* wnd, void *data1, void *data2) -> Uint32 {
                PlayerImpl * pl = (PlayerImpl*)data1;
                // set time
                if (!wnd) return interval;

                Label* label = Label::ToLabel(wnd->FindControl("label_now_time"));
                if (label) {
                    time_t rawtime;
                    time(&rawtime);
                    char pblgtime[20];
                    strftime(pblgtime, 20, "%X", localtime(&rawtime));
                    label->SetText(pblgtime);
                }

                if (pl) return pl->fillVideoData();

                return interval;
            }, this);
        if (*timer_video_ == 0) {
            Log("edui can't add video timer : %s", IEnginePtr->GetError());
            timer_video_ = nullptr;
            return false;
        }
    }

    {
        Button* button = Button::ToButton(window_->FindControl("close"));
        if (button){
            button->evt.click.connect(this, &PlayerImpl::onButtonCloseClick);
        }

        auto wndctrl = WndCtrl::ToWndCtrl(window_->FindControl("wndctrl"));
        if (wndctrl){
            wndctrl->SetWindow(nullptr);
            ctrlwnd_ = wndctrl->GetInsetWindow();
            if (ctrlwnd_) {
                ctrlwnd_->evt.onRender.connect(this, &PlayerImpl::onWndCtrlRender);
            }
        }
        
        Log("%ld", window_);
    }

    if (!decoder_ || !decoder_->Init()) {
        Log("Unable to init decoder.");
        return false;
    }

    return true;
}

void PlayerImpl::Quit() {
    this->Stop();
    decoder_ = nullptr;
    timer_video_ = nullptr;
    ctrlwnd_ = nullptr;
    window_ = nullptr;

    IEnginePtr->Quit();
}

void PlayerImpl::Run() {
    window_->ShowWindow();
    IEnginePtr->RunMsgLoop(-1);
}

bool PlayerImpl::Open(const std::string &file) {
    this->Close();
    file_ = file;

    if (!decoder_ || !decoder_->Open(file_))
        return false;

    bool b1 = this->openVideo();
    bool b2 = this->openAudio();

    decoder_->SetNoUse(!b1, !b2, false);

    if (!b1 && !b2) {
        this->Close();
        return false;
    }

    if (!b2 && b1) {
        video_only_ = true;
    }

    if (window_) {
        Label* label = Label::ToLabel(window_->FindControl("title_file_name"));
        if (label) {
            auto text = FilePath::NewFilePath(file);
            label->SetText(text->u8string().c_str());
        }
    }

    return true;
}

bool PlayerImpl::openVideo() {
    if (!ctrlwnd_ || !decoder_) return false;

    auto info = decoder_->getVideoInfo();
    if (!info) return false;

    info->format = av::ePixelFormat::yuv420p;
    info->algorithm = av::eAlgorithm::bicubic;
    decoder_->setInfo(info.get());

    window_->SetSize(info->width, info->height + 30);

    g_texture_video_ = IEnginePtr->GetTextureInterface();
    if (!g_texture_video_ 
            || !g_texture_video_->Create(ctrlwnd_->GetRender()
                    , info->width
                    , info->height
                    , TEXTUREACCESS_STREAMING
                    , std::to_string((uint64_t)g_texture_video_).c_str()) ) {
        if (g_texture_video_) g_texture_video_->Release();
        g_texture_video_ = nullptr;
        return false;
    }

    return true;
}

bool PlayerImpl::openAudio() {
    return false;
}

void PlayerImpl::setClock(double time) {
    clock_ = time;
}

Uint32 PlayerImpl::fillVideoData() {
    if (!ctrlwnd_ || !decoder_) return 40;

    /*
    *   check pointer
    */
    if (!this->decoder_ || !this->ctrlwnd_ || !/*this->*/g_texture_video_)
        return 40;

    if (!this->data_video_) {
        /*
        *   get data.
        */
        this->data_video_ = this->decoder_->pop_video_data();
        if (!this->data_video_) return 40;

        av::log("-");
    }

    // init interval as 10.
    Uint32 interval = 10;

    /*
    *   ensure the video synchronize with sound.
    */
    int32_t delay_time = this->delayTime(this->data_video_->time);
    if (delay_time > 1) {
        // video time is fast, need delay
        return interval;
    } else {
        // video time is the same as audio time.
        // video time is slow, show video quickly.

        /*
        *   set video data
        */
        {
            std::lock_guard<std::mutex> lock(g_mutex_texture_);
            g_texture_video_->UpdateYUV(
                      nullptr
                    , this->data_video_->data[0], this->data_video_->linesize[0]
                    , this->data_video_->data[1], this->data_video_->linesize[1]
                    , this->data_video_->data[2], this->data_video_->linesize[2] );
        }

        /*
        *   notify main thread to flush video.
        */
        {
            Msg evt;
            evt.msg = MSG_WND_EXPOSED;
            ctrlwnd_->PushMsg(&evt);
        }
        if (video_only_) this->setClock(this->data_video_->time);
        Logf(".");

        // video time is slow, show video quickly.
        if (delay_time < -1) {
            /*
            *   release data
            */
            this->data_video_ = nullptr;
            return interval;
        }

        // video time is the same as audio time.
        if (this->data_video_->delay > 0
                && this->data_video_->delay < 50) {
            /*
            *   release data
            */
            interval = this->data_video_->delay;
            this->data_video_ = nullptr;
            return interval;
        }
    
    }

    return interval;
}

double PlayerImpl::getClock() {
    return clock_;
}

int32_t PlayerImpl::delayTime(double time) {
    return (int32_t)((time - clock_) * 1000);
}

void PlayerImpl::Close() {
    clock_ = 0;
    video_only_ = false;
    data_audio_ = nullptr;
    data_video_ = nullptr;
    g_texture_video_ = nullptr;
    Log("%ld", window_);
}

void PlayerImpl::Play() {
    if (decoder_) return;
        decoder_->Run();
}

void PlayerImpl::Stop() {
    this->Close();
    if (decoder_) return;
        decoder_->Stop();
}

void PlayerImpl::Pause() {
    if (decoder_) return;
        decoder_->Pause();
}

void PlayerImpl::Continue() {
    if (decoder_) return;
        decoder_->Continue();
}

void PlayerImpl::Seek(double pos) {
    if (decoder_) return;
        decoder_->Seek(pos);
}

//-----------------------//
void PlayerImpl::onButtonCloseClick(Button* btn) {
    Log("%ld, %ld", this->window_, btn->GetWindow());
    btn->GetWindow()->Close();
}

void PlayerImpl::onHandleMsg(Msg*, bool*) {

}

void PlayerImpl::onWndCtrlRender(IRender* render, const Rect* rc, bool* bHandled) {
    {
        std::lock_guard<std::mutex> lock(g_mutex_texture_);
        render->DrawTexture(g_texture_video_, nullptr, nullptr);
    }
}

} // end namespace av
} // end namespace dzlua